package org.benetech.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.math.NumberUtils;
import org.benetech.event.EventListener;

/**
 * Utilities for handling character encoding issues.
 * @author Reuben Firmin
 */
public final class EncodingUtils {

	private static final String JUNK_CHAR = "";

	private static final Pattern XML_ESCAPE_UNICODE = Pattern.compile("&#x([0-9A-F]+);");

	/**
	 * Non-constructor.
	 */
	private EncodingUtils() {
		// no-op
	}

	/**
	 * Converts unicode characters to latin equivalents. Inserts {@link #JUNK_CHAR} for any characters it doesn't have
	 * a mapping for.
	 * @param string The string to make the replacements in.
	 * @param listener Event listener to report unmappable characters to.
	 * @return Never null.
	 */
	public static String stripUnicodeChars(final String string, final EventListener listener) {
		final StringBuffer out = new StringBuffer();
		for (int index = 0; index < string.length(); index++) {
			out.append(getLatinChar(string.charAt(index), listener));
		}
		return out.toString();
	}

	public static String stripEscapedUnicode(final String xmlString, final EventListener listener) {
		final StringBuffer out = new StringBuffer();
		final Matcher xmlFinder = XML_ESCAPE_UNICODE.matcher(xmlString);
		int lastIndex = 0;
		if (xmlFinder.find()) {
			boolean firstFind = true;
			while (firstFind || xmlFinder.find()) {
				out.append(xmlString.substring(lastIndex, xmlFinder.start()));
				final String sequence = xmlString.substring(xmlFinder.start(1), xmlFinder.end(1));
				final int i = Integer.parseInt(sequence, 16);
				// NoCheck - magic numbers
				if (i <= 127) {
					if (i == 0x0020) {
						out.append("&nbsp;");
					} else if (i == 0x0026) {
						out.append("&amp;");
					} else {
						out.append((char) i);
					}
				// CheckOn - no more magic, for a while
				} else {
					out.append(getLatinChar(i, listener));
				}
				lastIndex = xmlFinder.end();
				firstFind = false;
			}
			if (lastIndex < (xmlString.length() - 1)) {
				out.append(xmlString.substring(lastIndex));
			}
		} else {
			listener.message("No escaped unicode found.");
			out.append(xmlString);
		}
		return out.toString();
	}

	/**
	 * Get the string for this character. This will either be the character itself, a string replacement if we are
	 * downmapping it,
	 * @param character The character that needs to be made safe
	 * @param listener The event listener, to report to
	 * @return Never null
	 */
	// NoCheck - lots of magic numbers
	public static String getLatinChar(final int character, final EventListener listener) {
		String out = null;
		// see http://people.w3.org/rishida/scripts/uniview/ for pure joy / reference
		// we replace all possible characters between 127 and 255, since they break on windows computers. we also
		// replace all reasonable punctuation characters above 255, since they break in DAISY readers.
		if (character > 0x7f) {
			// skip x0080 - x00BF
			if (character >= 0x00C0 && character <= 0x00FF) {
				switch (character) {
					case 0x00C0: out = "A"; break;
					case 0x00C1: out = "A"; break;
					case 0x00C2: out = "A"; break;
					case 0x00C3: out = "A"; break;
					case 0x00C4: out = "A"; break;
					case 0x00C5: out = "A"; break;
					case 0x00C6: out = "AE"; break;
					case 0x00C7: out = "C"; break;
					case 0x00C8: out = "E"; break;
					case 0x00C9: out = "E"; break;
					case 0x00CA: out = "E"; break;
					case 0x00CB: out = "E"; break;
					case 0x00CC: out = "I"; break;
					case 0x00CD: out = "I"; break;
					case 0x00CE: out = "I"; break;
					case 0x00CF: out = "I"; break;
					case 0x00D1: out = "D"; break;
					case 0x00D2: out = "N"; break;
					case 0x00D3: out = "O"; break;
					case 0x00D4: out = "O"; break;
					case 0x00D5: out = "O"; break;
					case 0x00D6: out = "O"; break;
					case 0x00D7: out = "X"; break;
					case 0x00D8: out = "O"; break;
					case 0x00D9: out = "U"; break;
					case 0x00DA: out = "U"; break;
					case 0x00DB: out = "U"; break;
					case 0x00DC: out = "U"; break;
					case 0x00DD: out = "Y"; break;
					case 0x00DE: out = "P"; break;
					case 0x00DF: out = "B"; break;
					case 0x00E0: out = "a"; break;
					case 0x00E1: out = "a"; break;
					case 0x00E2: out = "a"; break;
					case 0x00E3: out = "a"; break;
					case 0x00E4: out = "a"; break;
					case 0x00E5: out = "a"; break;
					case 0x00E6: out = "ae"; break;
					case 0x00E7: out = "c"; break;
					case 0x00E8: out = "e"; break;
					case 0x00E9: out = "e"; break;
					case 0x00EA: out = "e"; break;
					case 0x00EB: out = "e"; break;
					case 0x00EC: out = "i"; break;
					case 0x00ED: out = "i"; break;
					case 0x00EE: out = "i"; break;
					case 0x00EF: out = "i"; break;
					case 0x00F0: out = "o"; break;
					case 0x00F1: out = "n"; break;
					case 0x00F2: out = "o"; break;
					case 0x00F3: out = "o"; break;
					case 0x00F4: out = "o"; break;
					case 0x00F5: out = "o"; break;
					case 0x00F6: out = "o"; break;
					case 0x00F7: out = "/"; break;
					case 0x00F8: out = "o"; break;
					case 0x00F9: out = "u"; break;
					case 0x00FA: out = "u"; break;
					case 0x00FB: out = "u"; break;
					case 0x00FC: out = "u"; break;
					case 0x00FD: out = "y"; break;
					case 0x00FE: out = "p"; break;
					case 0x00FF: out = "y"; break;
					default : out = getJunkChar((char) character, listener);
				}
			} else if (character >= 0x2010 && character <= 0x2055) {
				switch (character) {
					case 0x2010: out = "-"; break;
					case 0x2011: out = "-"; break;
					case 0x2012: out = "-"; break;
					case 0x2013: out = "-"; break;
					case 0x2014: out = "-"; break;
					case 0x2015: out = "-"; break;
					case 0x2016: out = "|"; break;
					case 0x2017: out = "-"; break;
					case 0x2018: out = "\'"; break;
					case 0x2019: out = "\'"; break;
					case 0x201A: out = ","; break;
					case 0x201B: out = "\'"; break;
					case 0x201C: out = "\""; break;
					case 0x201D: out = "\""; break;
					case 0x201E: out = "\""; break;
					case 0x201F: out = "\""; break;
					// skip 0x2020, 0x2021
					case 0x2022: out = "*"; break;
					case 0x2023: out = "*"; break;
					case 0x2024: out = "."; break;
					case 0x2025: out = ".."; break;
					case 0x2026: out = "..."; break;
					case 0x2027: out = "."; break;
					// skip 0x2028 - 0x2031
					case 0x2032: out = "\'"; break;
					case 0x2033: out = "\""; break;
					// skip 0x2034 - 0x2037
					case 0x2038: out = "^"; break;
					case 0x2039: out = "<"; break;
					case 0x203A: out = ">"; break;
					case 0x203B: out = "X"; break;
					case 0x203C: out = "!!"; break;
					case 0x203D: out = "?"; break;
					case 0x203E: out = "-"; break;
					// skip 0x203F - 0x2042
					case 0x2043: out = "-"; break;
					case 0x2044: out = "/"; break;
					case 0x2045: out = "\'"; break;
					// skip 0x2046
					case 0x2047: out = "??"; break;
					case 0x2048: out = "?!"; break;
					case 0x2049: out = "!?"; break;
					// skip 0x204A - 0x204D
					case 0x204E: out = "*"; break;
					case 0x204F: out = ";"; break;
					// skip 0x2050 - 0x2051
					case 0x2052: out = "%"; break;
					case 0x2053: out = "~"; break;
					// skip 0x2054
					case 0x2055: out = "*"; break;
					default : out = getJunkChar((char) character, listener);
				}
			} else if (character >= 0x207B && character <= 0x208E) {
				switch (character) {
					case 0x207B: out = "-"; break;
					case 0x207C: out = "-"; break;
					case 0x207D: out = "("; break;
					case 0x207E: out = ")"; break;
					// skip 0x207F - 0x208A
					case 0x208B: out = "-"; break;
					case 0x208C: out = "-"; break;
					case 0x208D: out = "("; break;
					case 0x208E: out = ")"; break;
					default : out = getJunkChar((char) character, listener);
				}
			} else if (character >= 0x3001 && character <= 0x301E) {
					switch (character) {
						case 0x3001: out = ","; break;
						// skip 12290
						case 0x3003: out = "\""; break;
						// skip 12292 - 12317
						case 0x301E: out = "\""; break;
						default : out = getJunkChar((char) character, listener);
					}
			} else if (character == 0x2122) {
				out = "TM";
			} else {
				out = getJunkChar((char) character, listener);
			}
		} else {
			out = "" + (char) character;
		}
		return out;
	}
	// CheckOn - no more magic numbers

	/**
	 * Return the junk character, and tell the listener that we couldn't deal with it.
	 * @param unmappable The unmappable character.
	 * @param listener The listener
	 * @return Never null
	 */
	private static String getJunkChar(final char unmappable, final EventListener listener) {
		listener.message("Cannot map unicode character: " + unmappable);
		return JUNK_CHAR;
	}
}
